{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b518b04cbfe0"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "906e07f6e562"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8bd329a4bbca"
      },
      "source": [
        "# Masking and padding with Keras"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8b208d0913b8"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/masking_and_padding\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/snapshot-keras/site/en/guide/keras/masking_and_padding.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/keras-team/keras-io/blob/master/guides/understanding_masking_and_padding.py\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs/site/en/guide/keras/masking_and_padding.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8d4ac441b1fc"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ec52be14e686"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3731f3ff3670"
      },
      "source": [
        "## 시작하기\n",
        "\n",
        "**마스킹** 은 시퀀스 처리 레이어에 입력의 특정 시간 단계가 누락되어 데이터를 처리 할 때 건너 뛰도록 지시하는 방법입니다.\n",
        "\n",
        "**패딩**은 마스킹된 스텝이 시퀀스의 시작 또는 도입부에 위치하는 특수한 형태의 마스킹입니다. 패딩이 필요한 이유는 시퀀스 데이터를 연속 배치로 인코딩해야 하는 데 있습니다. 배치의 모든 시퀀스를 지정된 표준 길이에 맞추려면 일부 시퀀스를 패딩 처리하거나 잘라내야 합니다.\n",
        "\n",
        "자세히 살펴 보자."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ac6b121d6be0"
      },
      "source": [
        "## 패딩 시퀀스 데이터\n",
        "\n",
        "시퀀스 데이터를 처리 할 때 개별 샘플의 길이가 다른 것이 매우 일반적입니다. 다음 예제를 고려하십시오 (텍스트로 단어로 토큰 화됨).\n",
        "\n",
        "```\n",
        "[\n",
        "  [\"Hello\", \"world\", \"!\"],\n",
        "  [\"How\", \"are\", \"you\", \"doing\", \"today\"],\n",
        "  [\"The\", \"weather\", \"will\", \"be\", \"nice\", \"tomorrow\"],\n",
        "]\n",
        "```\n",
        "\n",
        "어휘 조회 후 데이터는 다음과 같이 정수로 벡터화 될 수 있습니다.\n",
        "\n",
        "```\n",
        "[\n",
        "  [71, 1331, 4231]\n",
        "  [73, 8, 3215, 55, 927],\n",
        "  [83, 91, 1, 645, 1253, 927],\n",
        "]\n",
        "```\n",
        "\n",
        "데이터는 개별 샘플의 길이가 각각 3, 5 및 6인 중첩된 목록입니다. 딥 러닝 모델의 입력 데이터는 단일 텐서(이 경우, 예를 들어 `(batch_size, 6, vocab_size)` 형상의 텐서)여야 하므로 가장 긴 항목보다 짧은 샘플은 일부 자리 표시자 값으로 패딩 처리해야 합니다(또는, 짧은 샘플을 패딩 처리하기 전에 긴 샘플을 잘라낼 수도 있음).\n",
        "\n",
        "Keras는 Python 목록을 잘라서 공통 길이로 패딩 처리하는 유틸리티 기능을 제공합니다: `tf.keras.preprocessing.sequence.pad_sequences`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bb64fb185a05"
      },
      "outputs": [],
      "source": [
        "raw_inputs = [\n",
        "    [711, 632, 71],\n",
        "    [73, 8, 3215, 55, 927],\n",
        "    [83, 91, 1, 645, 1253, 927],\n",
        "]\n",
        "\n",
        "# By default, this will pad using 0s; it is configurable via the\n",
        "# \"value\" parameter.\n",
        "# Note that you could \"pre\" padding (at the beginning) or\n",
        "# \"post\" padding (at the end).\n",
        "# We recommend using \"post\" padding when working with RNN layers\n",
        "# (in order to be able to use the\n",
        "# CuDNN implementation of the layers).\n",
        "padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(\n",
        "    raw_inputs, padding=\"post\"\n",
        ")\n",
        "print(padded_inputs)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "03092b2da690"
      },
      "source": [
        "## 마스킹\n",
        "\n",
        "이제 모든 샘플의 길이가 균일하므로 데이터의 일부가 실제로 채워져 있다는 사실을 모델에 알려야합니다. 그 메커니즘은 **마스킹** 입니다.\n",
        "\n",
        "Keras 모델에서 입력 마스크를 도입하는 세 가지 방법이 있습니다.\n",
        "\n",
        "- `keras.layers.Masking` 레이어를 추가하십시오.\n",
        "- `mask_zero=True` 로 `keras.layers.Embedding` 레이어를 구성하십시오.\n",
        "- 이 인수를 지원하는 계층 (예 : RNN 계층)을 호출 할 때 `mask` 인수를 수동으로 전달하십시오."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6103601e5fff"
      },
      "source": [
        "## 마스크 생성 레이어 : `Embedding` 및 `Masking`\n",
        "\n",
        "후드 아래에서이 레이어는 마스크 텐서 (모양 `(batch, sequence_length)` 가진 2D 텐서)를 만들어 `Masking` 또는 `Embedding` 레이어에서 반환 한 텐서 출력에 연결합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b2363b293483"
      },
      "outputs": [],
      "source": [
        "embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)\n",
        "masked_output = embedding(padded_inputs)\n",
        "\n",
        "print(masked_output._keras_mask)\n",
        "\n",
        "masking_layer = layers.Masking()\n",
        "# Simulate the embedding lookup by expanding the 2D input to 3D,\n",
        "# with embedding dimension of 10.\n",
        "unmasked_embedding = tf.cast(\n",
        "    tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 10]), tf.float32\n",
        ")\n",
        "\n",
        "masked_embedding = masking_layer(unmasked_embedding)\n",
        "print(masked_embedding._keras_mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "17e4bdb563b2"
      },
      "source": [
        "인쇄 된 결과에서 볼 수 있듯이 마스크는 모양이 `(batch_size, sequence_length)` 인 2D 부울 텐서이며, 각 개별 `False` 항목은 처리 중에 해당 시간 단계를 무시해야 함을 나타냅니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cf11a0399fcf"
      },
      "source": [
        "## Mask propagation in the Functional API and Sequential API\n",
        "\n",
        "When using the Functional API or the Sequential API, a mask generated by an `Embedding` or `Masking` layer will be propagated through the network for any layer that is capable of using them (for example, RNN layers). Keras will automatically fetch the mask corresponding to an input and pass it to any layer that knows how to use it.\n",
        "\n",
        "For instance, in the following Sequential model, the `LSTM` layer will automatically receive a mask, which means it will ignore padded values:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0adbecda288a"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential(\n",
        "    [layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True), layers.LSTM(32),]\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a8ac6481a1d5"
      },
      "source": [
        "다음과 같은 기능적 API 모델의 경우에도 마찬가지입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f374ab06743d"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)\n",
        "outputs = layers.LSTM(32)(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2f2c4b96ecb5"
      },
      "source": [
        "## 마스크 텐서를 레이어로 직접 전달"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "11dccb581014"
      },
      "source": [
        "마스크를 처리 할 수있는 레이어 (예 : `LSTM` 레이어)는 `__call__` 메서드에 `mask` 인수가 있습니다.\n",
        "\n",
        "한편 마스크 (예 : `Embedding` )를 생성하는 레이어는 호출 할 수있는 `compute_mask(input, previous_mask)` 메소드를 노출합니다.\n",
        "\n",
        "따라서 마스크 생성 레이어의 `compute_mask()` 메서드 출력을 다음과 같이 마스크 소비 레이어의 `__call__` 메서드로 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1955aa63896b"
      },
      "outputs": [],
      "source": [
        "class MyLayer(layers.Layer):\n",
        "    def __init__(self, **kwargs):\n",
        "        super(MyLayer, self).__init__(**kwargs)\n",
        "        self.embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)\n",
        "        self.lstm = layers.LSTM(32)\n",
        "\n",
        "    def call(self, inputs):\n",
        "        x = self.embedding(inputs)\n",
        "        # Note that you could also prepare a `mask` tensor manually.\n",
        "        # It only needs to be a boolean tensor\n",
        "        # with the right shape, i.e. (batch_size, timesteps).\n",
        "        mask = self.embedding.compute_mask(inputs)\n",
        "        output = self.lstm(x, mask=mask)  # The layer will ignore the masked values\n",
        "        return output\n",
        "\n",
        "\n",
        "layer = MyLayer()\n",
        "x = np.random.random((32, 10)) * 100\n",
        "x = x.astype(\"int32\")\n",
        "layer(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b04dd330f848"
      },
      "source": [
        "## 사용자 정의 레이어에서 마스킹 지원"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8451a1a8ff27"
      },
      "source": [
        "때로는 마스크를 생성하는 레이어(예: `Embedding`) 또는 현재 마스크를 수정해야 하는 레이어를 작성해야 할 수도 있습니다.\n",
        "\n",
        "예를 들어, 시간 차원에서 연결된 `Concatenate` 레이어와 같이 입력과 다른 시간 차원을 가진 텐서를 생성하는 레이어는 다운스트림 레이어가 마스킹된 타임스텝을 올바르게 처리할 수 있도록 현재 마스크를 수정해야 합니다.\n",
        "\n",
        "To do this, your layer should implement the `layer.compute_mask()` method, which produces a new mask given the input and the current mask.\n",
        "\n",
        "Here is an example of a `TemporalSplit` layer that needs to modify the current mask."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a06fb2194c0d"
      },
      "outputs": [],
      "source": [
        "class TemporalSplit(keras.layers.Layer):\n",
        "    \"\"\"Split the input tensor into 2 tensors along the time dimension.\"\"\"\n",
        "\n",
        "    def call(self, inputs):\n",
        "        # Expect the input to be 3D and mask to be 2D, split the input tensor into 2\n",
        "        # subtensors along the time axis (axis 1).\n",
        "        return tf.split(inputs, 2, axis=1)\n",
        "\n",
        "    def compute_mask(self, inputs, mask=None):\n",
        "        # Also split the mask into 2 if it presents.\n",
        "        if mask is None:\n",
        "            return None\n",
        "        return tf.split(mask, 2, axis=1)\n",
        "\n",
        "\n",
        "first_half, second_half = TemporalSplit()(masked_embedding)\n",
        "print(first_half._keras_mask)\n",
        "print(second_half._keras_mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "282b867dcd95"
      },
      "source": [
        "입력 값에서 마스크를 생성 할 수있는 `CustomEmbedding` 레이어의 다른 예는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "e760655cd39c"
      },
      "outputs": [],
      "source": [
        "class CustomEmbedding(keras.layers.Layer):\n",
        "    def __init__(self, input_dim, output_dim, mask_zero=False, **kwargs):\n",
        "        super(CustomEmbedding, self).__init__(**kwargs)\n",
        "        self.input_dim = input_dim\n",
        "        self.output_dim = output_dim\n",
        "        self.mask_zero = mask_zero\n",
        "\n",
        "    def build(self, input_shape):\n",
        "        self.embeddings = self.add_weight(\n",
        "            shape=(self.input_dim, self.output_dim),\n",
        "            initializer=\"random_normal\",\n",
        "            dtype=\"float32\",\n",
        "        )\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.nn.embedding_lookup(self.embeddings, inputs)\n",
        "\n",
        "    def compute_mask(self, inputs, mask=None):\n",
        "        if not self.mask_zero:\n",
        "            return None\n",
        "        return tf.not_equal(inputs, 0)\n",
        "\n",
        "\n",
        "layer = CustomEmbedding(10, 32, mask_zero=True)\n",
        "x = np.random.random((3, 10)) * 9\n",
        "x = x.astype(\"int32\")\n",
        "\n",
        "y = layer(x)\n",
        "mask = layer.compute_mask(x)\n",
        "\n",
        "print(mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bb34149eb837"
      },
      "source": [
        "## 호환 가능한 레이어에서 전파를 마스크하도록 선택\n",
        "\n",
        "대부분의 레이어는 시간 차원을 수정하지 않으므로 현재 마스크를 수정할 필요가 없습니다. 그러나 그들은 여전히 현재 마스크를 변경하지 않고 다음 레이어로 **전파** 할 수 있기를 원할 수 있습니다. **이것은 옵트 인 동작입니다.** 기본적으로 사용자 정의 레이어는 현재 마스크를 제거합니다 (프레임 워크에서 마스크 전파가 안전한지 여부를 알 수있는 방법이 없기 때문에).\n",
        "\n",
        "시간 차원을 수정하지 않는 사용자 정의 레이어가 있고 현재 입력 마스크를 전파 할 수 있으려면 레이어 생성자에서 `self.supports_masking = True` 를 설정해야합니다. 이 경우 `compute_mask()` 의 기본 동작은 현재 마스크를 통과하는 것입니다.\n",
        "\n",
        "마스크 전파가 허용 된 레이어의 예는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "895c35534d06"
      },
      "outputs": [],
      "source": [
        "class MyActivation(keras.layers.Layer):\n",
        "    def __init__(self, **kwargs):\n",
        "        super(MyActivation, self).__init__(**kwargs)\n",
        "        # Signal that the layer is safe for mask propagation\n",
        "        self.supports_masking = True\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.nn.relu(inputs)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a2e1e0a81995"
      },
      "source": [
        "이제 마스크를 생성하는 레이어(예: `Embedding`)와 마스크를 소비하는 레이어(예: `LSTM`) 사이에서 이 사용자 정의 레이어를 사용하여 마스크 소비 레이어까지 마스크를 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "486e39e9a9a7"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)\n",
        "x = MyActivation()(x)  # Will pass the mask along\n",
        "print(\"Mask found:\", x._keras_mask)\n",
        "outputs = layers.LSTM(32)(x)  # Will receive the mask\n",
        "\n",
        "model = keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "55ab9c02f4ba"
      },
      "source": [
        "## 마스크 정보가 필요한 레이어 작성\n",
        "\n",
        "일부 레이어는 마스크 *소비자*입니다. 이러한 레이어는 `call`에서 `mask` 인수를 허용하고 이를 사용하여 특정 타임스텝을 건너뛸지 여부를 결정합니다.\n",
        "\n",
        "이러한 계층을 작성하려면 `call` 서명에 `mask=None` 인수를 추가하면됩니다. 입력과 관련된 마스크는 가능할 때마다 레이어로 전달됩니다.\n",
        "\n",
        "다음은 간단한 예입니다. 마스크 된 시간 단계를 버리고 입력 시퀀스의 시간 차원 (축 1)에 대한 소프트 맥스를 계산하는 레이어입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7809df539882"
      },
      "outputs": [],
      "source": [
        "class TemporalSoftmax(keras.layers.Layer):\n",
        "    def call(self, inputs, mask=None):\n",
        "        broadcast_float_mask = tf.expand_dims(tf.cast(mask, \"float32\"), -1)\n",
        "        inputs_exp = tf.exp(inputs) * broadcast_float_mask\n",
        "        inputs_sum = tf.reduce_sum(inputs * broadcast_float_mask, axis=1, keepdims=True)\n",
        "        return inputs_exp / inputs_sum\n",
        "\n",
        "\n",
        "inputs = keras.Input(shape=(None,), dtype=\"int32\")\n",
        "x = layers.Embedding(input_dim=10, output_dim=32, mask_zero=True)(inputs)\n",
        "x = layers.Dense(1)(x)\n",
        "outputs = TemporalSoftmax()(x)\n",
        "\n",
        "model = keras.Model(inputs, outputs)\n",
        "y = model(np.random.randint(0, 10, size=(32, 100)), np.random.random((32, 100, 1)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6373f43bbe18"
      },
      "source": [
        "## 요약\n",
        "\n",
        "이것이 Keras의 패딩 및 마스킹에 대해 알아야 할 전부입니다. 요약하자면:\n",
        "\n",
        "- \"Masking\"은 레이어가 시퀀스 입력에서 특정 시간 단계를 건너 뛰거나 무시할 때를 알 수있는 방법입니다.\n",
        "- 일부 레이어는 마스크 생성기입니다. `Embedding` 하면 입력 값에서 마스크를 생성 할 수 있으며 ( `mask_zero=True` ) `Masking` 레이어도 생성 할 수 있습니다.\n",
        "- 일부 레이어는 마스크 소비자입니다. `__call__` 메서드에 `mask` 인수를 표시합니다. 이것은 RNN 계층의 경우입니다.\n",
        "- Functional API 및 Sequential API에서 마스크 정보는 자동으로 전파됩니다.\n",
        "- 독립형 방식으로 레이어를 사용하는 경우 `mask` 인수를 레이어에 수동으로 전달할 수 있습니다.\n",
        "- 현재 마스크를 수정하거나 새 마스크를 생성하거나 입력과 관련된 마스크를 사용하는 레이어를 쉽게 작성할 수 있습니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "masking_and_padding.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
